1 module myers_diff;
2 
3 
4 import std.stdio;
5 import std.typecons;
6 import std.math;
7 import std.array;
8 import std.conv;
9 template MyersDiff(T)
10 {
11 private:
12     class V
13     {
14     private:
15         long[] i_;
16         long start_;
17         long end_;
18     public:
19         this(long start, long end)
20         {
21             i_.length = end - start + 1;
22             start_ = start;
23             end_ = end;
24         }
25         long* at(long idx)
26         {
27             return &i_[idx - start_];
28         }
29     };
30 private:
31     Tuple!(long, long, long, long, long) FindMiddleSnake(T[] a, T[] b)
32     {
33         long N = a.length;
34         long M = b.length;
35         long delta = N - M;
36         long MAX = (M + N) * 2;
37         auto fv = new V(-MAX, MAX);
38         auto rv = new V(-MAX, MAX);
39         long x, y;
40         (*fv.at(1)) = 0;
41         (*rv.at(delta + 1)) = N + 1;
42         for (long D = 0; D <= ceil((M + N) / 2.0); D++) 
43         {
44             for (long k = -D; k <= D; k += 2) 
45             {
46                 if (k == -D || (k != D && (*fv.at(k - 1)) < (*fv.at(k + 1)))) 
47                 {
48                     x = (*fv.at(k + 1));
49                 } 
50                 else 
51                 {
52                     x = (*fv.at(k - 1)) + 1;
53                 }
54                 y = x - k;
55                 while (x < N && y < M && a[x] == b[y]) 
56                 {
57                     ++x;
58                     ++y;
59                 }
60                 (*fv.at(k)) = x;
61                 if (delta % 2 != 0 && k >= delta - (D - 1) && k <= delta + D - 1) 
62                 {
63                     if ((*fv.at(k)) >= (*rv.at(k))) 
64                     {
65                         return tuple((*rv.at(k)), (*rv.at(k)) - k, x, y, 2 * D - 1);
66                     }
67                 }
68             }
69 
70             for (long k = -D + delta; k <= D + delta; k += 2) 
71             {
72                 if (k == -D + delta || (k != D + delta && (*rv.at(k - 1)) >= (*rv.at(k + 1)))) 
73                 {
74                     x = (*rv.at(k + 1)) - 1;
75                 } 
76                 else 
77                 {
78                     x = (*rv.at(k - 1));
79                 }
80                 y = x - k;
81                 while (x > 0 && y > 0 && a[x - 1] == b[y - 1]) 
82                 {
83                     x -= 1;
84                     y -= 1;
85                 }
86                 (*rv.at(k)) = x;
87                 if (delta % 2 == 0 && k >= -D && k <= D) 
88                 {
89                     if ((*fv.at(k)) >= (*rv.at(k))) 
90                     {
91                         return tuple(x, y, (*fv.at(k)), (*fv.at(k)) - k, 2 * D);
92                     }
93                 }
94             }
95         }
96         return tuple(long(0), long(0), long(0), long(0), long(0));
97     }
98 public:
99     enum EditType 
100     {
101         Add = 0,
102         Remove = 1
103     };
104     struct DiffResult 
105     {
106         long pos;
107         EditType type;
108         T str;
109     };
110     DiffResult[] getDiff(T[] a, T[] b, long offset = 0)
111     {
112         DiffResult[] rt;
113         long N = a.length;
114         long M = b.length;
115         long Min = N < M ? N : M;
116         long j = 0;
117         for (;j < Min && a[0] == b[0]; ++j) 
118         {
119             a = a[1..$];
120             b = b[1..$];
121             --N;
122             --M;
123         }
124         offset += j;
125         while (N > 0 && M > 0 && a[N - 1] == b[M - 1]) 
126         {
127             --N;
128             --M;
129         }
130         if (N > 0 && M > 0) 
131         {
132             auto t = FindMiddleSnake(a, b);
133             rt ~= getDiff(a[0..t[0]], b[0..t[1]], offset);
134             rt ~= getDiff(a[t[2]..$], b[t[3]..$], offset + t[2]);
135         } 
136         else if (N > 0) 
137         {
138             for (long i = 0; i < N; i++) 
139             {
140                 DiffResult itm;
141                 itm.pos = i + offset;
142                 itm.type = EditType.Remove;
143                 itm.str = a[i];
144                 rt ~= itm;
145             }
146         } 
147         else if (M > 0) 
148         {
149             for (long i = 0; i < M; i++) 
150             {
151                 DiffResult itm;
152                 itm.pos = N + offset;
153                 itm.type = EditType.Add;
154                 itm.str = b[i];
155                 rt ~= itm;
156             }
157         }
158         return rt;
159     }
160 private:
161     string formatOne(DiffResult r, string format, string strAdd, string strDelete)
162     {
163         string op = strAdd;
164         if (r.type == EditType.Remove)
165         {
166             op = strDelete;
167         }
168         string pos = to!string(r.pos);
169         string contest = to!string(r.str);
170         return format.replace("%pos%", pos).replace("%type%", op).replace("%content%", contest);
171     }
172 public:
173     string formatResult(DiffResult[] results, string format, string strAdd, string strDelete, size_t max_line = size_t.max)
174     {
175         string rt;
176         auto len = results.length < max_line ? results.length : max_line;
177         auto rest = results.length - len;
178         for (size_t i = 0; i < len; ++i)
179         {
180             rt ~= formatOne(results[i], format, strAdd, strDelete);
181         }
182         if (rest > 0)
183         {
184             string rest_str = (to!string(rest) ~ "... different remain");
185             string last_line = format.replace("%pos%", rest_str).replace("%type%", rest_str).replace("%content%", rest_str);
186             rt ~= last_line;
187         }
188         return rt;
189     }
190 };
191 
192 auto strDiff(string a, string b)
193 {
194     alias strDiff =  MyersDiff!char;
195     return strDiff.getDiff(a.dup, b.dup);
196 }
197 
198 auto strDiffFormat(MyersDiff!(char).DiffResult[]  result, string format, string strAdd, string strDelet, size_t max_line = size_t.max)
199 {
200     alias strDiff = MyersDiff!char;
201     return strDiff.formatResult(result, format, strAdd, strDelet, max_line);
202 }
203 
204 auto lineDiff(string a, string b)
205 {
206     alias lineDiff = MyersDiff!string;
207     auto array_a = a.replace("\r", "").split("\n");
208     auto array_b = b.replace("\r", "").split("\n");
209     return lineDiff.getDiff(array_a, array_b);
210 }
211 
212 auto lineDiffFormat(MyersDiff!(string).DiffResult[] result, string format, string strAdd, string strDelet, size_t max_line = size_t.max)
213 {
214     alias lineDiff = MyersDiff!string;
215     return lineDiff.formatResult(result, format, strAdd, strDelet, max_line);
216 }
217